﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;

namespace SimpleReactiveObjects
{
    /// <summary>
    /// Provides a set of extension methods for subscribing via weak reference.
    /// </summary>
    public static class WeakObservable
    {
        /// <summary>
        /// Returns an observable sequence that refers observers via weak reference.
        /// </summary>
        /// <typeparam name="TSource">The type source.</typeparam>
        /// <param name="source">The source sequence.</param>
        /// <returns>The observable sequence that refers observers via weak reference.</returns>
        public static IObservable<TSource> AsWeakObservable<TSource>(this IObservable<TSource> source)
        {
            var ret = Observable.Create<TSource>(observer =>
            {
                var weakObserver = new WeakObserver<TSource>(observer);
                var disposable = source.Subscribe(weakObserver);
                weakObserver.SetDisposable(disposable);

                var d = new Subscription<TSource>(observer, weakObserver, disposable);
                return d;
            });

            return ret;
        }

        private class WeakObserver<TSource> : IObserver<TSource>
        {
            private readonly WeakReference<IObserver<TSource>> observer;
            private readonly SingleAssignmentDisposable disposable;

            public WeakObserver(IObserver<TSource> observer)
            {
                this.observer = new WeakReference<IObserver<TSource>>(observer);
                this.disposable = new SingleAssignmentDisposable();
            }

            public void SetDisposable(IDisposable subscription)
            {
                disposable.Disposable = subscription;
            }

            public void OnCompleted()
            {
                IObserver<TSource> o;

                var b = observer.TryGetTarget(out o);
                if (b)
                {
                    o.OnCompleted();
                }
                else
                {
                    disposable.Dispose();
                }
            }

            public void OnError(Exception error)
            {
                IObserver<TSource> o;

                var b = observer.TryGetTarget(out o);
                if (b)
                {
                    o.OnError(error);
                }
                else
                {
                    disposable.Dispose();
                }
            }

            public void OnNext(TSource value)
            {
                IObserver<TSource> o;

                var b = observer.TryGetTarget(out o);
                if (b)
                {
                    o.OnNext(value);
                }
                else
                {
                    disposable.Dispose();
                }
            }
        }

        private class Subscription<TSource> : IDisposable
        {
            private readonly IObserver<TSource> observer;
            private readonly WeakObserver<TSource> weakObserver;
            private readonly IDisposable disposable;

            public Subscription(IObserver<TSource> observer, WeakObserver<TSource> weakObserver, IDisposable disposable)
            {
                this.observer = observer;
                this.weakObserver = weakObserver;
                this.disposable = disposable;
            }

            public void Dispose()
            {
                disposable.Dispose();
            }
        }
    }
}
